// sleepWave.js
import { SleepWaveBlock } from './sleepBlock'
import { SleepWaveLine } from './sleepLine'
import { Tooltip } from './tooltip'
import { throttle, isArray, isObject, uniq, isUndefined } from 'lodash-es'
import { randomColor, getIdealTenTimesNum } from './tool'
import {optionType} from "@/utils/sleepWave/sleepWave";

export class SleepWave {
  $el:HTMLElement |null = null
  canvas:HTMLCanvasElement|null = null
  ctx: CanvasRenderingContext2D | null = null
  tooltip:any = null
  eventList = ['click', 'mousemove']
  children = [] // 存放子元素

  // 图表显示相关主要数据
  types = ['深睡', '浅睡', '眼动', '清醒']
  colors = ['#FFB9E4', '#D3B4FF', '#9345FF', '#5701CD']
  data:any[] = []
  optionData:any[] = []

  // 可配置基本属性
  axisColor = '#808080'
  axisLabelColor = '#808080'
  axisWidthY = 70 // Y轴的宽度
  axisHeightX = 20 // X轴的高度
  paddingRight = 10 // 右侧留出的显示空间宽度
  blockHeight = 0 // 方块的高度，不配置就默认取disY - 4 * borderRadius
  borderRadius = 12 // 方形弧度
  lineWidth = 2 // 连线宽度
  labelAlign = 'right' // Y轴label文字对齐 left or right
  showAxisLabelX = false // 是否显示X轴坐标刻度
  maxX = 800 // X轴最大值
  minX = 0 // X轴最小值
  splitNumber = 5 // X轴刻度分割数
  markHeightX = 5 // X轴刻度高度
  animation = true // 是否开启动画，默认开启
  drawerFrequency = 20 // 动画帧数，决定动画快慢，默认20帧
  tooltipFormatter = null // 自定义tooltip
  xAxisFormatter:((value: number, index?:number) => string) | null | undefined = null// 自定义x轴label
  xAxisMarkVisible = true // 是否显示X轴刻度
  yAxisLabelVisible = true // 是否显示Y轴type

  // 不可配置的属性
  disX = 1 // 单位x的宽度
  disY = 1 // 单位y的高度

  // 初始化
  init(dom:HTMLElement) {
    this.$el = dom
    this.$el.classList.add('sleep-wave-canvas')
    document.styleSheets[0].insertRule('.sleep-wave-canvas { position: relative; overflow: auto }', 0);
    document.styleSheets[0].insertRule('.sleep-wave-canvas::-webkit-scrollbar { display:none }', 1);
    this.canvas = document.createElement('canvas')
    this.canvas.width = this.$el.clientWidth
    this.canvas.height = this.$el.clientHeight
    this.$el.appendChild(this.canvas)
    this.ctx = this.canvas.getContext('2d')
  }

  initTooltip() {
    this.tooltip = new Proxy(new Tooltip(this.$el, this.tooltipFormatter), {
      get: function (obj, prop) {
        return prop in obj ? obj[prop] : undefined
      },
      set: function (obj, prop, value) {
        if (prop === 'visible') {
          if (value && !obj.visible) {
            obj.show()
          }
          if (!value && obj.visible) {
            obj.hide()
          }
        }

        obj[prop] = value

        if (prop === 'blockInfo') {
          obj.updateSlotDom()
        }
        return true
      }
    })
  }

  // 设置option
  setOption(option:optionType) {
    // 清空整个画板
    this.ctx?.clearRect(0, 0, <number>this.canvas?.width, <number>this.canvas?.height)
    // 自定义属性
    const { data, types, colors, tooltipFormatter, xAxisFormatter } = option
    this.tooltipFormatter = tooltipFormatter
    this.xAxisFormatter = xAxisFormatter
    this.optionData = data
    this.setData(data, types, colors)
    this.initTooltip()

    this.borderRadius = option.borderRadius || this.borderRadius
    this.lineWidth = option.lineWidth || this.lineWidth
    this.splitNumber = option.splitNumber || this.splitNumber
    this.labelAlign = option.labelAlign || this.labelAlign
    this.markHeightX = option.markHeightX || this.markHeightX
    this.axisWidthY = option.axisWidthY || this.axisWidthY
    this.axisHeightX = option.axisHeightX || this.axisHeightX
    this.paddingRight = option.paddingRight || this.paddingRight
    this.blockHeight = option.blockHeight || this.blockHeight
    this.drawerFrequency = option.drawerFrequency || this.drawerFrequency
    this.animation = isUndefined(option.animation) ? this.animation : option.animation
    this.xAxisMarkVisible = isUndefined(option.xAxisMarkVisible) ? this.xAxisMarkVisible : option.xAxisMarkVisible
    this.yAxisLabelVisible = isUndefined(option.yAxisLabelVisible) ? this.yAxisLabelVisible : option.yAxisLabelVisible
    const dataMax = this.data.reduce((sum, item) => sum + item[0], 0)
    this.maxX = option.maxX || getIdealTenTimesNum(dataMax, this.splitNumber)
    this.maxX = option.data.reduce((sum, item)=>{
      return sum + item.value
    },0)

    // 根据maxX的字符长度增加paddingRight
    let maxXWidth = 0
    if (typeof this.xAxisFormatter === 'function') {
      maxXWidth = this.ctx?.measureText(String(this.xAxisFormatter(this.maxX))).width!
    } else {
      maxXWidth = this.ctx?.measureText(String(this.maxX)).width!
    }
    this.paddingRight += maxXWidth

    // 更新相关属性
    this.updateProperties()
    // 开始绘制
    this.draw()
    // 初始化事件
    this.initEvent()
  }

  // 根据data类型取值
  setData(data:any[], types?:any[], colors?:string[]) {
    if (types) {
      this.types = types
    }
    if (colors) {
      this.colors = colors
    }
    if (data.length <= 0) return

    // 如果数组元素的类型也是数组，则用第一位做时长，第二位做Y轴类型（0开始的整数）
    if (isArray(data[0])) {
      // 没传types则为index
      if (!types) {
        this.types = uniq(data.map(item => item[1]))
      }
      this.data = data.map(item => ([
        item[0],
        this.types.indexOf(item[1])
      ])).filter(item => this.types.indexOf(item[1]) > -1)
    }

    // 如果数组元素的类型是对象，则用 { label, value } 来做类型，并更新types
    else if (isObject(data[0])) {
      // 没传types则根据数据中包含的label来确定types
      if (!types) {
        this.types = uniq(data.map(item => item.label))
      }
      this.data = data.map(item => ([
        item.value,
        this.types.indexOf(item.label),
      ])).filter(item => item[1] > -1)
    }
    else {
      throw new TypeError('data is not valid in setOption')
    }

    if (this.colors.length < this.types.length) {
      for (let i = this.colors.length; i < this.types.length; i ++) {
        this.colors[i] = randomColor()!
      }
    }
  }

  // 更新相关属性
  updateProperties() {
    this.disX = (this.canvas!.width - this.axisWidthY - this.paddingRight) / (this.maxX - this.minX)
    this.disY = (this.canvas!.height - this.axisHeightX) / this.types.length
    this.blockHeight = this.blockHeight || (this.disY - 4 * this.borderRadius)
    // borderRadius最大为块高度的一半也就是绘图区高度的1/8
	  this.borderRadius = Math.min(this.disY / 4, this.borderRadius)
  }

  // canvas事件
  initEvent() {
    this.eventList.forEach(eventName => {
      // 高频率事件需要防抖
      this.canvas?.addEventListener(eventName, throttle(this.handleEvent, 40))
    })
  }

  handleEvent = (event) => {
    this.children
    .filter(shape => shape.isEventInRegion(event.x, event.y))
    .forEach(shape => shape.emit(event.type, event))
  }

  // 绘制
  draw() {
    // 绘制坐标
    this.drawAxis()
    // 绘制Y轴刻度
    this.drawAxisYLabel()
    // 绘制X轴刻度和值
    this.drawAxisXMark()
    // 绘制波形
    this.drawWave()
  }

  // 绘制波形
  drawWave() {
    this.data.reduce((pre, item) => {
      // 先画方形自己
      // 方框四个点位置
      const startX = pre ? pre[0] : this.axisWidthY
      const endX = startX + item[0] * this.disX
      const startY = (this.types.length - item[1] - 0.5) * this.disY - this.borderRadius - this.blockHeight / 2
      const endY = startY + this.blockHeight + 2 * this.borderRadius
      // 表示宽度是否够画直线，如果不够直接连着贝塞尔
      const hasStraight = (endX - startX) > 2 * this.borderRadius
      // 够不够画直线都要计算那个点的位置,X轴差值
      const nowRadius = hasStraight ? this.borderRadius : (endX - startX) / 2
      const { colors, types } = this

      // block块对象
      const block = new SleepWaveBlock(this, {
        startX,
        startY,
        endX,
        endY,
        nowRadius,
        value: item[0],
        title: types[item[1]],
        color: colors[item[1]],
      })
      block.draw()
      this.children.push(block)

      // 再连线
      if (pre && pre[1] !== item[1]) { // 一样高得情况不画线
        const line = new SleepWaveLine(this, {
          pre,
          startX,
          startY,
          endY,
          nowType: item[1],
          nowRadius,
        })
        line.draw()
      }
      return [endX, item[1], nowRadius] // 下一个pre为当前点的endx和item[1]，注意不是x不是item[0], 这里的第三个是上一次绘图的弧度
    }, null)
  }

  // 绘制坐标
  drawAxis() {
    this.ctx?.beginPath()
    this.ctx!.lineWidth = 0.5
    this.ctx!.strokeStyle = this.axisColor
    this.ctx?.moveTo(this.axisWidthY - 2, 0)
    this.ctx?.lineTo(this.axisWidthY - 2, this.canvas!.height - this.axisHeightX)
    this.ctx?.lineTo(this.canvas!.width - this.paddingRight, this.canvas!.height - this.axisHeightX)
    this.ctx?.stroke()
  }

  // 绘制Y轴type
  drawAxisYLabel() {
    this.ctx!.font = '14px PingFang SC'
    this.ctx!.fillStyle = this.axisLabelColor
    if (!this.yAxisLabelVisible) {
      return
    }
    const typesLen = this.types.length
    this.types.forEach((label, index) => {
      const textWidth = this.ctx?.measureText(label).width!
      // 问题过长还要省略显示
      if (textWidth > this.axisWidthY - 12) {
        let temp = ''
        const ellipseWidth = this.ctx!.measureText('...').width
        for (let i = 0; i < label.length; i ++) {
          if (this.ctx?.measureText(label.slice(0, i + 1)).width! + ellipseWidth <= this.axisWidthY - 12) {
            temp = label.slice(0, i + 1)
          }
        }
        const labelX = this.labelAlign === 'left' ? 4 : this.axisWidthY - this.ctx?.measureText(temp + '...').width! - 8
        this.ctx?.fillText(temp + '...', labelX, this.disY * (typesLen - index - 0.5))
      } else {
        const labelX = this.labelAlign === 'left' ? 4 : this.axisWidthY - textWidth - 8
        this.ctx?.fillText(label, labelX, this.disY * (typesLen - index - 0.5))
      }
    })
  }

  // 绘制X轴刻度和值
  drawAxisXMark() {
    const markStartX = this.axisWidthY - 2, markY = this.canvas!.height - this.axisHeightX - 1
    const markDis = (this.canvas!.width - markStartX - this.paddingRight) / this.splitNumber
    const markValDis = (this.maxX - this.minX) / this.splitNumber

    this.ctx!.font = '12px PingFang SC'
    this.ctx!.fillStyle = this.axisLabelColor
    // 最小刻度值先画
    console.log('this.data',this.data)
    const minText = this.optionData[0]?.begin || String(this.minX)
    const minTextWidth = this.ctx?.measureText(minText).width!
    this.ctx?.fillText(minText, markStartX - minTextWidth / 2, markY + 14)

    for (let i = 1; i <= this.splitNumber; i ++) {
      // 是否绘制刻度
      if (this.xAxisMarkVisible) {
        this.ctx?.beginPath()
        this.ctx!.lineWidth = 0.5
        this.ctx!.strokeStyle = this.axisColor
        this.ctx?.moveTo(markStartX + i * markDis - 1, markY)
        this.ctx?.lineTo(markStartX + i * markDis - 1, markY - this.markHeightX)
        this.ctx?.stroke()
      }

      this.ctx!.font = '12px PingFang SC'
      this.ctx!.fillStyle = this.axisLabelColor
      let markText = ''
      if (typeof this.xAxisFormatter === 'function') {
        console.log(this.minX + i * markValDis,i*this.splitNumber)
        markText = String(this.xAxisFormatter(this.minX + i * markValDis,i))
      } else {
        markText = String(this.minX + i * markValDis)
      }
      const markTextWidth = this.ctx?.measureText(markText).width!
      this.ctx?.fillText(markText, markStartX + i * markDis - markTextWidth / 2 - 1, markY + 14)
    }
  }
}

export const sleepWindow = new SleepWave();
window.$sleepWindow = sleepWindow;
